package com.hqd.ch03.v51.boot.context.properties;

import com.hqd.ch03.v51.boot.context.properties.bind.BindConstructorProvider;
import com.hqd.ch03.v51.boot.context.properties.bind.Bindable;
import com.hqd.ch03.v51.core.annotation.MergedAnnotations;
import com.hqd.ch03.v51.utils.BeanUtils;

import java.lang.reflect.Constructor;

class ConfigurationPropertiesBindConstructorProvider implements BindConstructorProvider {

    static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();

    @Override
    public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
        return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding);
    }

    Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
        if (type == null) {
            return null;
        }
        Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
        if (constructor == null && (isConstructorBindingType(type) || isNestedConstructorBinding)) {
            constructor = deduceBindConstructor(type);
        }
        return constructor;
    }

    private Constructor<?> findConstructorBindingAnnotatedConstructor(Class<?> type) {
        if (isKotlinType(type)) {
            Constructor<?> constructor = BeanUtils.findPrimaryConstructor(type);
            if (constructor != null) {
                return findAnnotatedConstructor(type, constructor);
            }
        }
        return findAnnotatedConstructor(type, type.getDeclaredConstructors());
    }

    private Constructor<?> findAnnotatedConstructor(Class<?> type, Constructor<?>... candidates) {
        Constructor<?> constructor = null;
        for (Constructor<?> candidate : candidates) {
            if (MergedAnnotations.from(candidate).isPresent(ConstructorBinding.class)) {
                constructor = candidate;
            }
        }
        return constructor;
    }

    private boolean isConstructorBindingType(Class<?> type) {
        return isImplicitConstructorBindingType(type) || isConstructorBindingAnnotatedType(type);
    }

    private boolean isImplicitConstructorBindingType(Class<?> type) {
        Class<?> superclass = type.getSuperclass();
        return (superclass != null) && "java.lang.Record".equals(superclass.getName());
    }

    private boolean isConstructorBindingAnnotatedType(Class<?> type) {
        return MergedAnnotations.from(type, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
                .isPresent(ConstructorBinding.class);
    }

    private Constructor<?> deduceBindConstructor(Class<?> type) {
        if (isKotlinType(type)) {
            return deducedKotlinBindConstructor(type);
        }
        Constructor<?>[] constructors = type.getDeclaredConstructors();
        if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
            return constructors[0];
        }
        return null;
    }

    private Constructor<?> deducedKotlinBindConstructor(Class<?> type) {
        Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
        if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) {
            return primaryConstructor;
        }
        return null;
    }

    private boolean isKotlinType(Class<?> type) {
        return false;
    }

}
